// MQ2CastWatch.cpp : Defines the entry point for the DLL application.
//

// PLUGIN_API is only to be used for callbacks.  All existing callbacks at this time
// are shown below. Remove the ones your plugin does not use.  Always use Initialize
// and Shutdown for setup and cleanup, do NOT do it in DllMain.



#include "../MQ2Plugin.h"
#include <vector>
#include <time.h>


PreSetup("MQ2CastWatch");


#define WATCH_CASTONREQUESTER   1
#define WATCH_CASTONOTHER       2
#define WATCH_FOLLOW			3
#define WATCH_STAY				4
#define WATCH_SIT				5
#define WATCH_INVITE			6
#define WATCH_FIZZLE			7
#define WATCH_SICKPET			8
#define WATCH_MELENPC			9
#define WATCH_BACKOFF			10

Blech CAST_WATCH('#');

const double pi_180 = 3.14159/180;

char *who;
char *category;
char *target;
char spell[MAX_STRING];
char spellgem[MAX_STRING];
char command[MAX_STRING];
char castcommand[MAX_STRING];
char temp[MAX_STRING];
bool bMovingToSomeplace=false;
float movingToX,movingToY;


char strSpawnWatchGeneral[MAX_STRING];
char strSpawnWatchZone[MAX_STRING];
vector<char *> vSpawnWatchList;

#define aCastWatch(List,WatchID,Filter) List.AddEvent(Filter,CastWatchEvent,(void*) WatchID);

void Execute(PCHAR zFormat, ...);
void GetCastCategory(char* category);
void DoCasting();

vector<char *> GetParameters(char*,char*);

void GetFormationInformation(char*,char*,char*,char*);
void GetFormationFromIni(char*,char*);
void Formation_TargetRadius(char*, char*);
void Formation_FixedLocation(char*, char*);
void InitializeSpawnWatchList();

bool istrstr(char* str,char *substr){
	char strSearchMe[MAX_STRING];
	char strSubStr[MAX_STRING];
	strcpy(strSearchMe,str);
	strcpy(strSubStr,substr);
	for(int i=0;i<strlen(strSearchMe);i++){
		strSearchMe[i]=tolower(strSearchMe[i]);
	}
	for(int i=0;i<strlen(strSearchMe);i++){
		strSubStr[i]=tolower(strSubStr[i]);
	}

	return strstr(strSearchMe,strSubStr) != NULL;
}
PLUGIN_API VOID SpawnWatchRefresh(PSPAWNINFO pChar, PCHAR Cmd) {
	InitializeSpawnWatchList();
}

PLUGIN_API VOID SpawnWatchList(PSPAWNINFO pChar, PCHAR Cmd) {
	vector<char *>::iterator iter = vSpawnWatchList.begin();
	WriteChatf("Watching for these spawns:");
	for(int i=0;i<vSpawnWatchList.capacity();i++){
		WriteChatf(" - \"%s\"",vSpawnWatchList.at(i));
	}
	/*while(iter != vSpawnWatchList.end()){
		WriteChatf(" - \"%s\"",*iter);
		iter++;
	}*/
}


PLUGIN_API VOID CastWatchFormationCommand(PSPAWNINFO pChar, PCHAR Cmd) {
	char *strFormationName;
	char *strFormationType;

	vector<char *> parameters;
	char strArg1[MAX_STRING];
	char strArg2[MAX_STRING];
	char strArg3[MAX_STRING];
	char strFormationParameters[MAX_STRING];

	strFormationName=strFormationType=NULL;

	GetArg(strArg1,Cmd,1);
	GetArg(strArg2,Cmd,2);
	GetArg(strArg3,Cmd,3);
	if(strlen(strArg1) && !strlen(strArg2) && !strlen(strArg3)){
		strFormationName = strArg1;
		//GetFormationInformation(strFormation,strFormationType,strDistance,strAngle);
		GetFormationFromIni(strFormationName,strFormationParameters);
		parameters = GetParameters(strFormationParameters," |,/\t");
		WriteChatf("size of parameter list: %d",parameters.size());
		for(int i=0;i<parameters.size();i++){
			WriteChatf(" params[%0d] = %s",i,parameters[i]);
		}
		if(parameters.size() > 0){
			strFormationType = parameters[0];
		}
	}
	if(!strFormationName || !strFormationType) return;
	if(stricmp(strFormationType,"target_radius")==0 || stricmp(strFormationType,"tr")==0){
		char *strDistance,*strAngle;
		strDistance=strAngle="";
		if(parameters.size() >=3){
			strDistance = parameters[1];
			strAngle	= parameters[2];
		}
		if(strlen(strDistance) > 0 && strlen(strAngle) > 0){
			Formation_TargetRadius(strDistance,strAngle);
		}
	}else if(stricmp(strFormationType,"fixed_location")==0 || stricmp(strFormationType,"fl")==0){
		char *y,*x;
		y=x="";
		if(parameters.size() >= 3){
			y=parameters[1];
			x=parameters[2];
			WriteChatf("y=%s, x=%s",y,x);
		}
		if(strlen(y) > 0 && strlen(x) > 0){
			Formation_FixedLocation(y,x);
		}
	}else{
		WriteChatf("Unknown formation type: %s",strFormationType);
	}
}
vector<char *> GetParameters(char *strSource, char *strDelimeters){
	vector<char *> result;
	//char param[MAX_STRING];
	char *pch;

	if(!strlen(strSource) || !strlen(strDelimeters)) return result;
	
	pch = strtok(strSource,strDelimeters);
	while(pch != NULL){
//		strcpy(param,pch);
		result.push_back(pch);
		pch = strtok(NULL,strDelimeters);
	}
	return result;
}

void Formation_TargetRadius(char *strDistance,char *strAngle){
	double distance,angle,heading,Y,X;
	PSPAWNINFO pMyTarget = ((PSPAWNINFO)pTarget);
	
	if(!pMyTarget) return;

	distance=atof(strDistance);
	angle=atof(strAngle);
	heading = pMyTarget->Heading*0.703125;
    //if (heading>=512.0f) heading -= 512.0f;
    //if (heading<0.0f) heading += 512.0f;
	X = pMyTarget->X - distance * sin((heading+angle)*pi_180);
	Y = pMyTarget->Y - distance * cos((heading+angle)*pi_180);

	Execute("/moveto loc %f %f",Y,X);
	movingToX = (float)X;
	movingToY = (float)Y;
	bMovingToSomeplace = true;
}

void Formation_FixedLocation(char *y,char *x){
	double Y,X;
	WriteChatf("FixedLocation: %s %s",y,x);
	if(strlen(x) == 0 || strlen(y) == 0) return;

	Y = atof(y);
	X = atof(x);
	
	WriteChatf("FixedLocation: %f %f",Y,X);

	Execute("/moveto loc %f %f",Y,X);
	movingToX = (float)X;
	movingToY = (float)Y;
	bMovingToSomeplace = true;
}

PLUGIN_API VOID CastWatchCommand(PSPAWNINFO pChar, PCHAR Cmd) {
	char MyCommand[MAX_STRING];
	char WhatToCast[MAX_STRING];
	char WhoToCastOn[MAX_STRING];
	GetArg(MyCommand,Cmd,1);
	if(stricmp(MyCommand,"c")==0 || stricmp(MyCommand,"ce")==0){//cast (using category) or cast exact spell
		GetArg(WhatToCast,Cmd,2);
		GetArg(WhoToCastOn,Cmd,3);
		target=WhoToCastOn;
		category=WhatToCast;
		if(stricmp(MyCommand,"c")==0)
			GetCastCategory(category);
		DoCasting();
	}else if(stricmp(MyCommand,"f")==0){//follow
		GetArg(WhoToCastOn,Cmd,2);
		if(WhoToCastOn){
			sprintf(command,"/target pc %s",WhoToCastOn);
			Execute(command);
			Execute("/stick 5");
		}		
	}else if(stricmp(MyCommand,"s")==0){//sit
		if(GetCharInfo()->standstate==STANDSTATE_STAND)
				Execute("/sit on");
	}else if(stricmp(MyCommand,"h")==0){//halt (stand still)
		Execute("/stick off");
	}else if (stricmp(MyCommand,"p")==0){//sick pets
		GetArg(WhoToCastOn,Cmd,2);
		Execute("/stick off");
		if(IsNumber(WhoToCastOn))
			sprintf(command,"/target id %s",WhoToCastOn);
		else
			sprintf(command,"/target npc \"%s\"",WhoToCastOn);
		Execute(command);
		Execute("/pet attack");
	}else if (stricmp(MyCommand,"b")==0){//back off
		Execute("/pet back off");
		Execute("/attack off");
	}else if (stricmp(MyCommand,"m")==0){//melee target
		GetArg(WhoToCastOn,Cmd,2);
		if(IsNumber(WhoToCastOn))
			sprintf(command,"/target id %s",WhoToCastOn);
		else
			sprintf(command,"/target npc \"%s\"",WhoToCastOn);
		Execute(command);
		if (GetCharInfo()->standstate==STANDSTATE_SIT){
			Execute("/stand");
			Execute("/delay 5");
		}
		Execute("/killthis");
	}
}

void GetFormationFromIni(char* formation,char *parameters){
	char section[MAX_STRING];
	//char parameters[MAX_STRING];
	sprintf(section,"%s_Formations",GetCharInfo()->Name);
	GetPrivateProfileString(section,formation,NULL,parameters,MAX_STRING,INIFileName);
	
	if(strlen(parameters)){
		WriteChatf("params: %s",parameters);
	}else{
		WriteChatf("params: [no parameters found]");
	}
}

void GetFormationInformation(char* formation,char*type,char*radius,char*angle){
	char section[MAX_STRING];
	char parameters[MAX_STRING];
	sprintf(section,"%s_Formations",GetCharInfo()->Name);
	GetPrivateProfileString(section,formation,NULL,parameters,MAX_STRING,INIFileName);
	WriteChatf("params: %s",parameters);
	if(!strlen(parameters)){
		type=NULL;
		radius=NULL;
		angle=NULL;
	}else{
		sscanf(parameters,"%s %s %s",type,radius,angle);
		WriteChatf("params: type: %s, radius: %s, angle: %s",type, radius, angle);
	}
}

void GetCastCategory(char* category){
	GetPrivateProfileString(GetCharInfo()->Name,category,NULL,spell,MAX_STRING,INIFileName);
	sprintf(spellgem,"%sGem",category);//reuse spellgem later to hold the actual value
	GetPrivateProfileString(GetCharInfo()->Name,spellgem,"gem1",spellgem,MAX_STRING,INIFileName);
	if(!strlen(spell))
		sprintf(spell,"%s",category); //assume that category is actually the spell name
	return;
}

void Execute(PCHAR zFormat, ...) {
  char zOutput[MAX_STRING]={0}; va_list vaList; va_start(vaList,zFormat);
  vsprintf(zOutput,zFormat,vaList); if(!zOutput[0]) return;
  DoCommand(GetCharInfo()->pSpawn,zOutput);
}
void DoCasting(){
	WriteChatf("Category: %s, Spell: %s, SpellGem: %s",category,spell,spellgem);
	if(spell){
		Execute("/stick off");
		if(IsNumber(target)){
			//sprintf(command,"/target id %s",target);			
			sprintf(command,"/casting \"%s\" %s -targetid|%s -maxtries|10",spell,spellgem,target);
			Execute(command);
		}else{
			sprintf(command,"/target \"%s\"",target);
			Execute(command);
			sprintf(command,"/casting \"%s\" %s -maxtries|10",spell,spellgem);
			sprintf(castcommand,"%s",command); //used in case of fizzle and another command comes in that is not a cast command
			Execute(command);
		}
/*		Execute(command);
		sprintf(command,"/casting \"%s\" %s 11s",spell,spellgem);
		sprintf(castcommand,"%s",command); //used in case of fizzle and another command comes in that is not a cast command
		Execute(command);
		*/
	}else{
		WriteChatf("Spell %s not found.",spell);
	}
}
void __stdcall CastWatchEvent(unsigned int ID, void *WatchID, PBLECHVALUE pValues){	
	WriteChatf("CastWatchEvent: ID: %X, pData: %X)",ID,WatchID);
	switch((unsigned int)WatchID){
		case WATCH_CASTONREQUESTER: //first one is who, second is what category/spell
			if(pValues && pValues->pNext){
				who=pValues->Value;
				target=who;
				category=pValues->pNext->Value;
				GetCastCategory(category);
				DoCasting();
			}else return; //bad request
			break;
		case WATCH_CASTONOTHER:
			if(pValues && pValues->pNext && pValues->pNext->pNext){
				who=pValues->Value;
				category=pValues->pNext->Value;
				target=pValues->pNext->pNext->Value;
				GetCastCategory(category);
				DoCasting();
			} else return; //bad request
			break;
		case WATCH_FOLLOW:
			if(!pValues) return; //bad request

			sprintf(command,"/target pc %s",pValues->Value);
			Execute(command);
			Execute("/stick 5");
			break;
		case WATCH_STAY:			
			Execute("/stick off");
			break;
		case WATCH_SIT:
			if(GetCharInfo()->standstate==STANDSTATE_STAND)
				Execute("/sit on");
			break;
		case WATCH_INVITE:
			Execute("/invite");
			break;
		case WATCH_FIZZLE:
			WriteChatf("Fizzle detection activated: executing %s",castcommand);
			Execute("/delay 10");
			Execute(castcommand);
			break;
		case WATCH_SICKPET:
			if(pValues && pValues->pNext){
				who=pValues->Value;
				target=pValues->pNext->Value;
				Execute("/stick off");
				if(IsNumber(target))
					sprintf(command,"/target id %s",target);
				else
					sprintf(command,"/target npc \"%s\"",target);
				Execute(command);
				Execute("/pet attack");
			}else return; //bad request
			break;
		case WATCH_MELENPC:
			if(pValues && pValues->pNext){
				who=pValues->Value;
				target=pValues->pNext->Value;
//				Execute("/stick off");
				if(IsNumber(target))
					sprintf(command,"/target id %s",target);
				else
					sprintf(command,"/target npc \"%s\"",target);
				Execute(command);
				if (GetCharInfo()->standstate==STANDSTATE_SIT){
					Execute("/stand");
					Execute("/delay 5");
				}
				Execute("/killthis");
			}else return; //bad request
			break;
		case WATCH_BACKOFF:
			Execute("/attack off");
			break;
		default:
			return;
	}	
}

void InitializeSpawnWatchList(){
	
	char *pch;
	char *strDelimeters=",|\t";

	vSpawnWatchList.clear();
	
	//Get the general spawn list (any zone)
	GetPrivateProfileString("Spawn Lists","General","",strSpawnWatchGeneral,MAX_STRING,INIFileName);
	
	
	pch = strtok(strSpawnWatchGeneral,strDelimeters);
	while(pch != NULL){
		WriteChatf("Adding to spawn list: %s.",pch);
		vSpawnWatchList.push_back(pch);
		pch = strtok(NULL,strDelimeters);
	}
	//Get the current zone's spawn list (short name)

	if(pZoneInfo != NULL){
		GetPrivateProfileString("Spawn Lists",((PZONEINFO)pZoneInfo)->ShortName,"",strSpawnWatchZone,MAX_STRING,INIFileName);
		pch = strtok(strSpawnWatchZone,strDelimeters);
		while(pch != NULL){
			WriteChatf("Adding to spawn list: %s.",pch);
			vSpawnWatchList.push_back(pch);		
			pch = strtok(NULL,strDelimeters);
		}
	}
}

// Called once, when the plugin is to initialize
PLUGIN_API VOID InitializePlugin(VOID)
{
	DebugSpewAlways("Initializing MQ2CastWatch");
	aCastWatch(CAST_WATCH,WATCH_CASTONREQUESTER,"#who# tells you, '(#category#)'");
	aCastWatch(CAST_WATCH,WATCH_CASTONREQUESTER,"#who# tells the group, '(#category#)'");
	aCastWatch(CAST_WATCH,WATCH_CASTONOTHER,"#who# tells you, '!#category# #other#'");
	aCastWatch(CAST_WATCH,WATCH_CASTONOTHER,"#who# tells the group, '!#category# #other#'");
	aCastWatch(CAST_WATCH,WATCH_MELENPC,"#who# tells you, 'mele #mob#'");
	aCastWatch(CAST_WATCH,WATCH_MELENPC,"#who# tells the group, 'mele #mob#'");
	aCastWatch(CAST_WATCH,WATCH_BACKOFF,"#who# tells you, 'back off'");
	aCastWatch(CAST_WATCH,WATCH_BACKOFF,"#who# tells the group, 'back off'");
	aCastWatch(CAST_WATCH,WATCH_SICKPET,"#who# tells you, 'sick #mob#'");
	aCastWatch(CAST_WATCH,WATCH_SICKPET,"#who# tells the group, 'sick #mob#'");
	aCastWatch(CAST_WATCH,WATCH_FOLLOW,"#who# tells you, 'follow'");
	aCastWatch(CAST_WATCH,WATCH_FOLLOW,"#who# tells the group, 'follow'");
	aCastWatch(CAST_WATCH,WATCH_STAY,"#who# tells you, 'stay'");
	aCastWatch(CAST_WATCH,WATCH_STAY,"#who# tells the group, 'stay'");
	aCastWatch(CAST_WATCH,WATCH_SIT,"#who# tells you, 'sit'");
	aCastWatch(CAST_WATCH,WATCH_SIT,"#who# tells the group, 'sit'");
	aCastWatch(CAST_WATCH,WATCH_INVITE,"#who# invites you to join a group.");
//	aCastWatch(CAST_WATCH,WATCH_FIZZLE,"Your spell fizzles!");
	// Add commands, MQ2Data items, hooks, etc.
	// AddCommand("/mycommand",MyCommand);
	AddCommand("/cw",CastWatchCommand);
	AddCommand("/cwf",CastWatchFormationCommand);
	AddCommand("/swr",SpawnWatchRefresh);
	AddCommand("/swl",SpawnWatchList);
	
	// AddXMLFile("MQUI_MyXMLFile.xml");
	// bmMyBenchmark=AddMQ2Benchmark("My Benchmark Name");

	InitializeSpawnWatchList();

}

// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID)
{
	DebugSpewAlways("Shutting down MQ2CastWatch");
	RemoveCommand("/cw");
	RemoveCommand("/cwf");
	RemoveCommand("/swr");
	RemoveCommand("/swl");
	// Remove commands, MQ2Data items, hooks, etc.
	// RemoveMQ2Benchmark(bmMyBenchmark);
	// RemoveCommand("/mycommand");
	// RemoveXMLFile("MQUI_MyXMLFile.xml");
}

// Called after entering a new zone
PLUGIN_API VOID OnZoned(VOID)
{
	DebugSpewAlways("MQ2CastWatch::OnZoned()");
	InitializeSpawnWatchList();
}

// Called once directly before shutdown of the new ui system, and also
// every time the game calls CDisplay::CleanGameUI()
PLUGIN_API VOID OnCleanUI(VOID)
{
	DebugSpewAlways("MQ2CastWatch::OnCleanUI()");
	// destroy custom windows, etc
}

// Called once directly after the game ui is reloaded, after issuing /loadskin
PLUGIN_API VOID OnReloadUI(VOID)
{
	DebugSpewAlways("MQ2CastWatch::OnReloadUI()");
	// recreate custom windows, etc
}

// Called every frame that the "HUD" is drawn -- e.g. net status / packet loss bar
PLUGIN_API VOID OnDrawHUD(VOID)
{
	// DONT leave in this debugspew, even if you leave in all the others
//	DebugSpewAlways("MQ2CastWatch::OnDrawHUD()");
}

// Called once directly after initialization, and then every time the gamestate changes
PLUGIN_API VOID SetGameState(DWORD GameState)
{
	DebugSpewAlways("MQ2CastWatch::SetGameState()");
	// if (GameState==GAMESTATE_INGAME)
	// create custom windows if theyre not set up, etc
}


// This is called every time MQ pulses
PLUGIN_API VOID OnPulse(VOID)
{
	if(bMovingToSomeplace){
		PCHARINFO pChar = GetCharInfo();
		if(abs(pChar->pSpawn->X - movingToX) < 10 && abs(pChar->pSpawn->Y - movingToY) < 10){
			pChar->pSpawn->X = movingToX; // /moveto loc y x gets you close to where you want to go, but not precisely
			pChar->pSpawn->Y = movingToY; // with a lot of characters, finely controlling where they are is nice, so this
			bMovingToSomeplace = false;   // teleports them a very short distance once they are pretty close.
			Execute("/face fast");
		}
	}
	// DONT leave in this debugspew, even if you leave in all the others
//	DebugSpewAlways("MQ2CastWatch::OnPulse()");
}

// This is called every time WriteChatColor is called by MQ2Main or any plugin,
// IGNORING FILTERS, IF YOU NEED THEM MAKE SURE TO IMPLEMENT THEM. IF YOU DONT
// CALL CEverQuest::dsp_chat MAKE SURE TO IMPLEMENT EVENTS HERE (for chat plugins)
PLUGIN_API DWORD OnWriteChatColor(PCHAR Line, DWORD Color, DWORD Filter)
{
	DebugSpewAlways("MQ2CastWatch::OnWriteChatColor(%s)",Line);
	return 0;
}

// This is called every time EQ shows a line of chat with CEverQuest::dsp_chat,
// but after MQ filters and chat events are taken care of.
PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color)
{
	DebugSpewAlways("MQ2CastWatch::OnIncomingChat(%s)",Line);
	CAST_WATCH.Feed(Line);
	return 0;
}

// This is called each time a spawn is added to a zone (inserted into EQ's list of spawns),
// or for each existing spawn when a plugin first initializes
// NOTE: When you zone, these will come BEFORE OnZoned
PLUGIN_API VOID OnAddSpawn(PSPAWNINFO pNewSpawn)
{
	char strMsg[MAX_STRING];
	DebugSpewAlways("MQ2CastWatch::OnAddSpawn(%s)",pNewSpawn->Name);
	for(vector<char *>::iterator iter=vSpawnWatchList.begin(); iter != vSpawnWatchList.end(); iter++){
		if(istrstr(pNewSpawn->DisplayedName,*iter)){
			time_t rawtime;
			time(&rawtime);
			sprintf(strMsg,"Spawn %s (level %0d) has SPAWNED at loc %.2f, %.2f - %s",pNewSpawn->DisplayedName,pNewSpawn->Level,pNewSpawn->Y,pNewSpawn->X,asctime(localtime(&rawtime)));
			WriteChatf(strMsg);
			Execute("/popup %s",strMsg);
			Execute("/beep");
		}
	}
}

// This is called each time a spawn is removed from a zone (removed from EQ's list of spawns).
// It is NOT called for each existing spawn when a plugin shuts down.
PLUGIN_API VOID OnRemoveSpawn(PSPAWNINFO pSpawn)
{
	char strMsg[MAX_STRING];
	for(vector<char *>::iterator iter=vSpawnWatchList.begin(); iter != vSpawnWatchList.end(); iter++){
		if(istrstr(pSpawn->DisplayedName,*iter)){
			time_t rawtime;
			time(&rawtime);
			sprintf(strMsg,"Spawn %s (level %0d) has DESPAWNED at loc %.2f, %.2f - %s",pSpawn->DisplayedName,pSpawn->Level,pSpawn->Y,pSpawn->X,asctime(localtime(&rawtime)));
			WriteChatf(strMsg);
			Execute("/popup %s",strMsg);
			Execute("/beep");
		}
	}
}

// This is called each time a ground item is added to a zone
// or for each existing ground item when a plugin first initializes
// NOTE: When you zone, these will come BEFORE OnZoned
PLUGIN_API VOID OnAddGroundItem(PGROUNDITEM pNewGroundItem)
{
	DebugSpewAlways("MQ2CastWatch::OnAddGroundItem(%d)",pNewGroundItem->DropID);
}

// This is called each time a ground item is removed from a zone
// It is NOT called for each existing ground item when a plugin shuts down.
PLUGIN_API VOID OnRemoveGroundItem(PGROUNDITEM pGroundItem)
{
	DebugSpewAlways("MQ2CastWatch::OnRemoveGroundItem(%d)",pGroundItem->DropID);
}
